﻿// <copyright file="ObservableCollection.cs" company="Ucaya">
// </copyright>
// <author>Ucaya</author>

namespace KinectExtensions.Collections.ObjectModel
{
    #region

    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;

    #endregion

    /// <summary>
    ///   Represents a dynamic data collection that provides notifications when items
    ///   get added, removed, or when the entire list is refreshed.
    /// </summary>
    /// <typeparam name = "T">type of the collection</typeparam>
    public class ObservableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T>
    {
        #region Constants and Fields

        /// <summary>
        ///   Locks the CollectionChanged event.
        /// </summary>
        private bool lockCollectionChangeEvent;

        private int lockCount;

        #endregion

        #region Constructors and Destructors

        public ObservableCollection()
        {
        }

        public ObservableCollection(IEnumerable<T> items)
        {
            this.AddRange(items);
        }

        #endregion

        #region Public Events

        public event EventHandler<CustomCollectionChangedEventArgs> CustomCollectionChanged;

        #endregion

        #region Public Methods

        /// <summary>
        ///   Adds and removes a range of items to the collection in one shot.
        /// </summary>
        /// <param name = "rangeToAdd">The range to add.</param>
        /// <param name = "rangeToemove">The range to remove.</param>
        public void AddAndRemoveRange(IEnumerable<T> rangeToAdd, IEnumerable<T> rangeToRemove)
        {
            bool itemChanged = false;
            T[] addArray = rangeToAdd.ToArray();
            T[] removeArray = rangeToRemove.ToArray();
            this.lockCollectionChangeEvent = true;
            try
            {
                foreach (var item in addArray)
                {
                    this.Add(item);
                    itemChanged = true;
                }

                foreach (var item in removeArray)
                {
                    this.Remove(item);
                    itemChanged = true;
                }
            }
            finally
            {
                this.lockCollectionChangeEvent = false;
            }

            if (itemChanged)
            {
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                this.OnCustomCollectionChanged(new CustomCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, addArray, removeArray, -1, -1));
            }
        }

        /// <summary>
        ///   Adds a range of items to the collection in one shot.
        /// </summary>
        /// <param name = "range">The range.</param>
        public void AddRange(IEnumerable<T> range)
        {
            bool itemAdded = false;

            this.lockCollectionChangeEvent = true;
            T[] addArray = range.ToArray();
            int addIdx = this.Count;
            try
            {
                foreach (var item in addArray)
                {
                    this.Add(item);
                    itemAdded = true;
                }
            }
            finally
            {
                this.lockCollectionChangeEvent = false;
            }

            if (itemAdded)
            {
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                this.OnCustomCollectionChanged(new CustomCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, addArray, null, addIdx, -1));
            }
        }

        /// <summary>
        ///   Adds a range of items to the collection in one shot.
        /// </summary>
        /// <param name = "range">The range.</param>
        public void InsertRange(IEnumerable<T> range, int index)
        {
            bool itemAdded = false;

            this.lockCollectionChangeEvent = true;
            T[] addArray = range.ToArray();
            try
            {
                int insertIdx = index;
                foreach (var item in addArray)
                {
                    this.Insert(insertIdx, item);
                    insertIdx++;
                    itemAdded = true;
                }
            }
            finally
            {
                this.lockCollectionChangeEvent = false;
            }

            if (itemAdded)
            {
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                this.OnCustomCollectionChanged(new CustomCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, addArray, null, index, -1));
            }
        }

        public void LockCollectionChanged()
        {
            this.lockCount++;
        }

        /// <summary>
        ///   Removes a range of items to the collection in one shot.
        /// </summary>
        /// <param name = "range">The range.</param>
        public void RemoveRange(IEnumerable<T> range)
        {
            bool itemRemoved = false;

            this.lockCollectionChangeEvent = true;
            T[] removedArray = range.ToArray();
            try
            {
                foreach (var item in removedArray)
                {
                    this.Remove(item);
                    itemRemoved = true;
                }
            }
            finally
            {
                this.lockCollectionChangeEvent = false;
            }

            if (itemRemoved)
            {
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                this.OnCustomCollectionChanged(new CustomCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, removedArray, -1, -1));
            }
        }

        /// <summary>
        ///   Replace the whole collection in one shot.
        /// </summary>
        /// <param name = "range">The new colletion content.</param>
        public void Replace(IEnumerable<T> range)
        {
            bool itemChanged = false;

            this.lockCollectionChangeEvent = true;
            T[] oldArray = this.ToArray();
            T[] newArray = range.ToArray();
            try
            {
                itemChanged = this.Count != 0;

                this.Clear();

                if (range != null)
                {
                    foreach (var item in newArray)
                    {
                        this.Add(item);
                        itemChanged = true;
                    }
                }
            }
            finally
            {
                this.lockCollectionChangeEvent = false;
            }

            if (itemChanged)
            {
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                this.OnCustomCollectionChanged(new CustomCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, newArray, oldArray, 0, 0));
            }
        }

        /// <summary>
        ///   Reset the content of the list.
        /// </summary>
        /// <param name = "newList">The new list content.</param>
        public void Reset(IEnumerable<T> newList)
        {
            this.lockCollectionChangeEvent = true;
            T[] oldArray = this.ToArray();
            T[] newArray = newList.ToArray();
            try
            {
                this.Clear();
                foreach (var item in newArray)
                {
                    this.Add(item);
                }
            }
            finally
            {
                this.lockCollectionChangeEvent = false;
            }

            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            this.OnCustomCollectionChanged(new CustomCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, newArray, oldArray, 0, 0));
        }

        public void UnlockCollectionChanged()
        {
            bool needRefresh = false;
            if (this.lockCount == 1)
            {
                needRefresh = true;
            }
            this.lockCount--;
            if (needRefresh)
            {
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                this.OnCustomCollectionChanged(new CustomCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
        }

        #endregion

        #region Methods

        /// <summary>
        ///   Raises the <see cref = "E:System.Collections.ObjectModel.ObservableCollection`1.CollectionChanged" /> event with the provided event data.
        /// </summary>
        /// <param name = "e">The event data to report in the event.</param>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (this.lockCount == 0 && !this.lockCollectionChangeEvent)
            {
                base.OnCollectionChanged(e);
            }
        }

        protected virtual void OnCustomCollectionChanged(CustomCollectionChangedEventArgs e)
        {
            if (this.lockCount == 0 && !this.lockCollectionChangeEvent)
            {
                if (this.CustomCollectionChanged != null)
                {
                    this.CustomCollectionChanged(this, e);
                }
            }
        }

        #endregion
    }
}